﻿#region Copyright
// Copyright (c) 2011-2012 RaisingForce Team (Alikin Sergey)
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
#endregion

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using RaisingForce.Raise.Language.RStates;
using RaisingForce.Raise.Language.Rules;

namespace RaisingForce.Raise.Language.Model
{
    sealed public class WordTypePool :
        IWordTypePool,
        IEnumerable<WordType>
    {
        #region Constructors

        public WordTypePool(bool registerDefaultWordTypes = false)
        {
            if (registerDefaultWordTypes)
            {
                this.RegisterDefaultWordTypes();
            }
        }

        #endregion

        #region Private Declarations

        private readonly WordTypeCollection wordTypes =
            new WordTypeCollection();

        [ContractInvariantMethod]
        private void ObjectInvariant()
        {
            Contract.Invariant(this.wordTypes != null);
            Contract.Invariant(Contract.ForAll(this.wordTypes, word => word != null));
        }

        #endregion

        #region Private Default Word Types

        private static readonly IfWordType lookForNothingWordType =
            new IfWordType(
                name: WordTypeNames.LookForNothing,
                primaryGlyphType: typeof(DirectionGlyph),
                secondaryGlyphType: null,
                alterableStateTypes: new Type[] { 
                    typeof(ObjectPosition) },
                priority: 10);

        private static readonly IfWordType lookForObjectWordType =
            new IfWordType(
                name: WordTypeNames.LookForObject,
                primaryGlyphType: typeof(DirectionGlyph),
                secondaryGlyphType: typeof(ObjectGlyph),
                alterableStateTypes: new Type[] { 
                    typeof(ObjectPosition) },
                priority: 20);

        private static readonly IfWordType lookForBorderWordType =
            new IfWordType(
                name: WordTypeNames.LookForBorder,
                primaryGlyphType: typeof(DirectionGlyph),
                secondaryGlyphType: typeof(ZeroGlyph),
                alterableStateTypes: null,
                priority: 30);

        private static readonly IfWordType counterIncrementingWordType =
            new IfWordType(
                name: WordTypeNames.CounterIncrementing,
                primaryGlyphType: typeof(CounterGlyph),
                secondaryGlyphType: null,
                alterableStateTypes: null,
                priority: 60);

        private static readonly IfWordType counterBoundaryWordType =
            new IfWordType(
                name: WordTypeNames.CounterBoundary,
                primaryGlyphType: typeof(CounterGlyph),
                secondaryGlyphType: typeof(NumberGlyph),
                alterableStateTypes: new Type[] { 
                    typeof(CounterState), 
                    typeof(CounterValue) },
                priority: 70);

        private static readonly IfWordType counterInitializedWordType =
            new IfWordType(
                name: WordTypeNames.CounterInitialized,
                primaryGlyphType: typeof(CounterGlyph),
                secondaryGlyphType: typeof(ZeroGlyph),
                alterableStateTypes: null,
                priority: 80);

        private static readonly IfWordType objectNearWordType =
            new IfWordType(
                name: WordTypeNames.ObjectNear,
                primaryGlyphType: typeof(ObjectGlyph),
                secondaryGlyphType: null,
                alterableStateTypes: null,
                priority: 40);

        private static readonly IfWordType objectThereWordType =
            new IfWordType(
                name: WordTypeNames.ObjectThere,
                primaryGlyphType: typeof(ObjectGlyph),
                secondaryGlyphType: typeof(DirectionGlyph),
                alterableStateTypes: null,
                priority: 50);

        private static readonly IfWordType comeFromChainWordType =
            new IfWordType(
                name: WordTypeNames.ComeFromChain,
                primaryGlyphType: typeof(ChainGlyph),
                secondaryGlyphType: null,
                alterableStateTypes: null,
                priority: 90);

        private static readonly ThenWordType moveWordType =
            new ThenWordType(
                name: WordTypeNames.Move,
                primaryGlyphType: typeof(DirectionGlyph),
                secondaryGlyphType: null,
                alterableStateTypes: new Type[] { 
                    typeof(ObjectPosition) });

        private static readonly ThenWordType moveTwiceWordType =
            new ThenWordType(
                name: WordTypeNames.MoveTwice,
                primaryGlyphType: typeof(DirectionGlyph),
                secondaryGlyphType: typeof(DirectionGlyph),
                alterableStateTypes: new Type[] { 
                    typeof(ObjectPosition) });

        private static readonly ThenWordType moveAndIncrementWordType =
            new ThenWordType(
                name: WordTypeNames.MoveAndIncrement,
                primaryGlyphType: typeof(DirectionGlyph),
                secondaryGlyphType: typeof(CounterGlyph),
                alterableStateTypes: new Type[] {
                    typeof(ObjectPosition), 
                    typeof(CounterValue) });

        private static readonly ThenWordType moveAndGoToChainWordType =
            new ThenWordType(
                name: WordTypeNames.MoveAndGoToChain,
                primaryGlyphType: typeof(DirectionGlyph),
                secondaryGlyphType: typeof(ChainGlyph),
                alterableStateTypes: new Type[] {
                    typeof(PreviousChain),
                    typeof(CurrentChain), 
                    typeof(CounterState),
                    typeof(CounterValue),
                    typeof(ObjectPosition) });

        private static readonly ThenWordType incrementCounterWordType =
            new ThenWordType(
                name: WordTypeNames.IncrementCounter,
                primaryGlyphType: typeof(CounterGlyph),
                secondaryGlyphType: null,
                alterableStateTypes: new Type[] {
                    typeof(CounterValue) });

        private static readonly ThenWordType incrementCounterTwiceWordType =
            new ThenWordType(
                name: WordTypeNames.IncrementCounterTwice,
                primaryGlyphType: typeof(CounterGlyph),
                secondaryGlyphType: typeof(CounterGlyph),
                alterableStateTypes: new Type[] {
                    typeof(CounterValue) });

        private static readonly ThenWordType incrementAndMoveWordType =
            new ThenWordType(
                name: WordTypeNames.IncrementAndMove,
                primaryGlyphType: typeof(CounterGlyph),
                secondaryGlyphType: typeof(DirectionGlyph),
                alterableStateTypes: new Type[] {
                    typeof(CounterValue),
                    typeof(ObjectPosition) });

        private static readonly ThenWordType initializeCounterWordType =
            new ThenWordType(
                name: WordTypeNames.InitializeCounter,
                primaryGlyphType: typeof(CounterGlyph),
                secondaryGlyphType: typeof(ZeroGlyph),
                alterableStateTypes: new Type[] {
                    typeof(CounterState),
                    typeof(CounterValue),
                    typeof(CounterInitializedState) });

        private static readonly ThenWordType setCounterWordType =
            new ThenWordType(
                name: WordTypeNames.SetCounter,
                primaryGlyphType: typeof(CounterGlyph),
                secondaryGlyphType: typeof(NumberGlyph),
                alterableStateTypes: new Type[] {
                    typeof(CounterValue) });

        private static readonly ThenWordType bypassObjectWordType =
            new ThenWordType(
                name: WordTypeNames.BypassObject,
                primaryGlyphType: typeof(ObjectGlyph),
                secondaryGlyphType: typeof(DirectionGlyph),
                alterableStateTypes: new Type[] {
                    typeof(ObjectPosition) });

        private static readonly ThenWordType moveToObjectWordType =
            new ThenWordType(
                name: WordTypeNames.MoveToObject,
                primaryGlyphType: typeof(DirectionGlyph),
                secondaryGlyphType: typeof(ObjectGlyph),
                alterableStateTypes: new Type[] {
                    typeof(ObjectPosition) });

        private static readonly ThenWordType goToChainWordType =
            new ThenWordType(
                name: WordTypeNames.GoToChain,
                primaryGlyphType: typeof(ChainGlyph),
                secondaryGlyphType: null,
                alterableStateTypes: new Type[] {
                    typeof(PreviousChain),
                    typeof(CurrentChain),
                    typeof(CounterState),
                    typeof(CounterValue),
                    typeof(ObjectPosition) });

        private static readonly ChainWordType chainNameWordType =
            new ChainWordType(
                name: WordTypeNames.ChainName,
                primaryGlyphType: typeof(ChainGlyph));

        #endregion

        #region Properties

        public int WordTypeCount
        {
            get
            {
                Contract.Ensures(Contract.Result<int>() >= 0);
                return this.wordTypes.Count;
            }
        }

        #endregion

        #region Public Methods

        [Pure]
        public bool ContainsWordType(WordType wordType)
        {
            return this.wordTypes.Contains(wordType);
        }

        public void RegisterWordType(WordType wordType)
        {
            if (wordType == null)
            {
                throw new ArgumentNullException("wordType");
            }
            if (this.ContainsWordType(wordType)
                || this.ContainsName(wordType.Name))
            {
                throw new ArgumentException("wordType");
            }
            Contract.EndContractBlock();
            this.wordTypes.Add(wordType);
        }

        public void UnregisterWordType(WordType wordType)
        {
            if (wordType == null)
            {
                throw new ArgumentNullException("wordType");
            }
            if (!this.ContainsWordType(wordType)
                || !this.ContainsName(wordType.Name))
            {
                throw new ArgumentException("wordType");
            }
            Contract.EndContractBlock();
            this.wordTypes.Remove(wordType);
        }

        public bool TryRegisterWordType(WordType wordType)
        {
            Contract.Requires(wordType != null);
            if (this.ContainsWordType(wordType)
                || this.ContainsName(wordType.Name))
            {
                return false;
            }
            this.wordTypes.Add(wordType);
            return true;
        }

        public bool RegisterDefaultWordTypes()
        {
            // warning: using bitwise OR, not logical OR!
            return this.TryRegisterWordType(WordTypePool.lookForNothingWordType)
                | this.TryRegisterWordType(WordTypePool.lookForObjectWordType)
                | this.TryRegisterWordType(WordTypePool.lookForBorderWordType)
                | this.TryRegisterWordType(WordTypePool.counterIncrementingWordType)
                | this.TryRegisterWordType(WordTypePool.counterBoundaryWordType)
                | this.TryRegisterWordType(WordTypePool.counterInitializedWordType)
                | this.TryRegisterWordType(WordTypePool.objectNearWordType)
                | this.TryRegisterWordType(WordTypePool.objectThereWordType)
                | this.TryRegisterWordType(WordTypePool.comeFromChainWordType)
                | this.TryRegisterWordType(WordTypePool.moveWordType)
                | this.TryRegisterWordType(WordTypePool.moveTwiceWordType)
                | this.TryRegisterWordType(WordTypePool.moveAndIncrementWordType)
                | this.TryRegisterWordType(WordTypePool.moveAndGoToChainWordType)
                | this.TryRegisterWordType(WordTypePool.incrementCounterWordType)
                | this.TryRegisterWordType(WordTypePool.incrementCounterTwiceWordType)
                | this.TryRegisterWordType(WordTypePool.incrementAndMoveWordType)
                | this.TryRegisterWordType(WordTypePool.initializeCounterWordType)
                | this.TryRegisterWordType(WordTypePool.setCounterWordType)
                | this.TryRegisterWordType(WordTypePool.bypassObjectWordType)
                | this.TryRegisterWordType(WordTypePool.moveToObjectWordType)
                | this.TryRegisterWordType(WordTypePool.goToChainWordType)
                | this.TryRegisterWordType(WordTypePool.chainNameWordType);
        }

        #endregion

        #region IWordTypePool Members

        [Pure]
        public bool ContainsName(string wordTypeName)
        {
            return this.wordTypes.Contains(wordTypeName);
        }

        public WordType GetWordType(string wordTypeName)
        {
            if (!WordTypeRules.NameIsValid(wordTypeName)
                || !this.ContainsName(wordTypeName))
            {
                throw new ArgumentException("wordTypeName");
            }
            return this.wordTypes[wordTypeName];
        }

        public WordType GetWordType(
            Type primaryGlyphType,
            Type secondaryGlyphType,
            WordContext context)
        {
            if (primaryGlyphType == null)
            {
                throw new ArgumentNullException("primaryGlyphType");
            }
            if (!primaryGlyphType.IsSubclassOf(typeof(Glyph)))
            {
                throw new ArgumentException("primaryGlyphType");
            }
            if (secondaryGlyphType != null
                && !secondaryGlyphType.IsSubclassOf(typeof(Glyph)))
            {
                throw new ArgumentException("secondaryGlyphType");
            }
            return this.wordTypes.FirstOrDefault(
                wordType => wordType.Context == context
                && wordType.PrimaryGlyphType == primaryGlyphType
                && wordType.SecondaryGlyphType == secondaryGlyphType);
        }

        public IList<string> GetAllowedWordTypeNames()
        {
            return this.wordTypes
                .Select(wordType => wordType.Name)
                .ToArray();
        }

        #endregion

        #region IEnumerable<WordType> Members

        public IEnumerator<WordType> GetEnumerator()
        {
            return this.wordTypes.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        #endregion
    }
}
